<!doctype html>
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
  <title>iron-a11y-keys</title>

  <script src="../../webcomponentsjs/webcomponents-lite.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <script src="../../iron-test-helpers/mock-interactions.js"></script>

  <link rel="import" href="../../polymer/polymer.html">
  <link rel="import" href="../iron-a11y-keys-behavior.html">
</head>
<body>
  <test-fixture id="BasicKeys">
    <template>
      <x-a11y-basic-keys></x-a11y-basic-keys>
    </template>
  </test-fixture>

  <test-fixture id="NonPropagatingKeys">
    <template>
      <x-a11y-basic-keys stop-keyboard-event-propagation></x-a11y-basic-keys>
    </template>
  </test-fixture>

  <test-fixture id="ComboKeys">
    <template>
      <x-a11y-combo-keys></x-a11y-combo-keys>
    </template>
  </test-fixture>

  <test-fixture id="AlternativeEventKeys">
    <template>
      <x-a11y-alternate-event-keys></x-a11y-alternate-event-keys>
    </template>
  </test-fixture>

  <test-fixture id="BehaviorKeys">
    <template>
      <x-a11y-behavior-keys></x-a11y-behavior-keys>
    </template>
  </test-fixture>

  <test-fixture id="PreventKeys">
    <template>
      <x-a11y-prevent-keys></x-a11y-prevent-keys>
    </template>
  </test-fixture>

  <script>
    suite('Polymer.IronA11yKeysBehavior', function() {
      var keys;

      suiteSetup(function() {
        /** @polymerBehavior */
        var KeysTestBehavior = [
          Polymer.IronA11yKeysBehavior,
          {
            properties: {keyCount: {type: Number, value: 0}},

            _keyHandler: function(event) {
              this.keyCount++;
              this.lastEvent = event;
            },

            // Same as _keyHandler, used to distinguish who's called before who.
            _keyHandler2: function(event) {
              this.keyCount++;
              this.lastEvent = event;
            },

            _preventDefaultHandler: function(event) {
              event.preventDefault();
              this.keyCount++;
              this.lastEvent = event;
            }
          }
        ];

        Polymer({
          is: 'x-a11y-basic-keys',

          behaviors: [KeysTestBehavior],

          keyBindings:
              {'space': '_keyHandler', '@': '_keyHandler', 'esc': '_keyHandler'}
        });

        Polymer({
          is: 'x-a11y-combo-keys',

          behaviors: [KeysTestBehavior],

          keyBindings: {
            'enter': '_keyHandler2',
            'ctrl+shift+a shift+enter': '_keyHandler'
          }
        });

        Polymer({
          is: 'x-a11y-alternate-event-keys',

          behaviors: [KeysTestBehavior],

          keyBindings: {'space:keyup': '_keyHandler'}
        });

        /** @polymerBehavior */
        var XA11yBehavior = {keyBindings: {'enter': '_keyHandler'}};

        Polymer({
          is: 'x-a11y-behavior-keys',

          behaviors: [KeysTestBehavior, XA11yBehavior],

          keyBindings: {'enter': '_keyHandler'}
        });

        Polymer({
          is: 'x-a11y-prevent-keys',

          behaviors: [KeysTestBehavior, XA11yBehavior],

          keyBindings: {
            'space a': '_keyHandler',
            'enter shift+a': '_preventDefaultHandler'
          }
        });
      });

      suite('basic keys', function() {
        setup(function() {
          keys = fixture('BasicKeys');
        });

        test('trigger the handler when the specified key is pressed', function() {
          MockInteractions.pressSpace(keys);

          expect(keys.keyCount).to.be.equal(1);
        });

        test('keyEventTarget can be null, and disables listeners', function() {
          keys.keyEventTarget = null;
          MockInteractions.pressSpace(keys);

          expect(keys.keyCount).to.be.equal(0);
        });

        test(
            'trigger the handler when the specified key is pressed together with a modifier',
            function() {
              var event = new CustomEvent('keydown');
              event.ctrlKey = true;
              event.keyCode = event.code = 32;
              keys.dispatchEvent(event);
              expect(keys.keyCount).to.be.equal(1);
            });

        test('handles special character @', function() {
          MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], '@');

          expect(keys.keyCount).to.be.equal(1);
        });

        test('handles variations of Esc key', function() {
          MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], 'Esc');
          expect(keys.keyCount).to.be.equal(1);

          MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], 'Escape');
          expect(keys.keyCount).to.be.equal(2);

          MockInteractions.pressAndReleaseKeyOn(keys, 27, [], '');
          expect(keys.keyCount).to.be.equal(3);
        });

        test('do not trigger the handler for non-specified keys', function() {
          MockInteractions.pressEnter(keys);

          expect(keys.keyCount).to.be.equal(0);
        });

        test('can have bindings added imperatively', function() {
          keys.addOwnKeyBinding('enter', '_keyHandler');

          MockInteractions.pressEnter(keys);
          expect(keys.keyCount).to.be.equal(1);

          MockInteractions.pressSpace(keys);
          expect(keys.keyCount).to.be.equal(2);
        });

        test('can remove imperatively added bindings', function() {
          keys.addOwnKeyBinding('enter', '_keyHandler');
          keys.removeOwnKeyBindings();

          MockInteractions.pressEnter(keys);
          expect(keys.keyCount).to.be.equal(0);

          MockInteractions.pressSpace(keys);
          expect(keys.keyCount).to.be.equal(1);
        });

        test('allows propagation beyond the key combo handler', function() {
          var keySpy = sinon.spy();
          document.addEventListener('keydown', keySpy);

          MockInteractions.pressEnter(keys);

          expect(keySpy.callCount).to.be.equal(1);
        });

        suite('edge cases', function() {
          test('knows that `spacebar` is the same as `space`', function() {
            var event = new CustomEvent('keydown');
            event.key = 'spacebar';
            expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true);
          });

          test('handles `+`', function() {
            var event = new CustomEvent('keydown');
            event.key = '+';
            expect(keys.keyboardEventMatchesKeys(event, '+')).to.be.equal(true);
          });

          test('handles `:`', function() {
            var event = new CustomEvent('keydown');
            event.key = ':';
            expect(keys.keyboardEventMatchesKeys(event, ':')).to.be.equal(true);
          });

          test('handles ` ` (space)', function() {
            var event = new CustomEvent('keydown');
            event.key = ' ';
            expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true);
          });
        });

        suite('matching keyboard events to keys', function() {
          test('can be done imperatively', function() {
            var event = new CustomEvent('keydown');
            event.keyCode = 65;
            expect(keys.keyboardEventMatchesKeys(event, 'a')).to.be.equal(true);
          });

          test('can be done with a provided keyboardEvent', function() {
            var event;
            MockInteractions.pressSpace(keys);
            event = keys.lastEvent;

            expect(event.detail.keyboardEvent).to.be.okay;
            expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true);
          });

          test('can handle variations in arrow key names', function() {
            var event = new CustomEvent('keydown');
            event.key = 'up';
            expect(keys.keyboardEventMatchesKeys(event, 'up')).to.be.equal(true);
            event.key = 'ArrowUp';
            expect(keys.keyboardEventMatchesKeys(event, 'up')).to.be.equal(true);
          });

          test('can handle function keys', function() {
            var event = new CustomEvent('keydown');
            event.keyCode = 112;
            expect(keys.keyboardEventMatchesKeys(event, 'f1')).to.be.equal(true);
            event.keyCode = 123;
            expect(keys.keyboardEventMatchesKeys(event, 'f12')).to.be.equal(true);
          });
        });

        suite(
            'matching keyboard events to top row and number pad digit keys',
            function() {
              test('top row can be done imperatively', function() {
                var event = new CustomEvent('keydown');
                event.keyCode = 49;
                expect(keys.keyboardEventMatchesKeys(event, '1')).to.be.equal(true);
              });

              test('number pad digits can be done imperatively', function() {
                var event = new CustomEvent('keydown');
                event.keyCode = 97;
                expect(keys.keyboardEventMatchesKeys(event, '1')).to.be.equal(true);
              });
            });
      });

      suite('combo keys', function() {
        setup(function() {
          keys = fixture('ComboKeys');
        });

        test('trigger the handler when the combo is pressed', function() {
          var event = new CustomEvent('keydown');

          event.ctrlKey = true;
          event.shiftKey = true;
          event.keyCode = event.code = 65;

          keys.dispatchEvent(event);

          expect(keys.keyCount).to.be.equal(1);
        });

        test('check if KeyBoardEvent.key is alpha-numberic', function() {
          var event = new CustomEvent('keydown');

          event.ctrlKey = true;
          event.shiftKey = true;
          event.key = 'A';

          keys.dispatchEvent(event);

          expect(keys.keyCount).to.be.equal(1);
        });

        test('trigger also bindings without modifiers', function() {
          var event = new CustomEvent('keydown');
          // Combo `shift+enter`.
          event.shiftKey = true;
          event.keyCode = event.code = 13;
          keys.dispatchEvent(event);
          expect(keys.keyCount).to.be.equal(2);
        });

        test('give precendence to combos with modifiers', function() {
          var enterSpy = sinon.spy(keys, '_keyHandler2');
          var shiftEnterSpy = sinon.spy(keys, '_keyHandler');
          var event = new CustomEvent('keydown');
          // Combo `shift+enter`.
          event.shiftKey = true;
          event.keyCode = event.code = 13;
          keys.dispatchEvent(event);
          expect(enterSpy.called).to.be.true;
          expect(shiftEnterSpy.called).to.be.true;
          expect(enterSpy.calledAfter(shiftEnterSpy)).to.be.true;
        });
      });

      suite('alternative event keys', function() {
        setup(function() {
          keys = fixture('AlternativeEventKeys');
        });

        test('trigger on the specified alternative keyboard event', function() {
          MockInteractions.keyDownOn(keys, 32);

          expect(keys.keyCount).to.be.equal(0);

          MockInteractions.keyUpOn(keys, 32);

          expect(keys.keyCount).to.be.equal(1);
        });
      });

      suite('behavior keys', function() {
        setup(function() {
          keys = fixture('BehaviorKeys');
        });

        test('bindings in other behaviors are transitive', function() {
          MockInteractions.pressEnter(keys);
          expect(keys.keyCount).to.be.equal(2);
        });
      });

      suite('stopping propagation automatically', function() {
        setup(function() {
          keys = fixture('NonPropagatingKeys');
        });

        test('does not propagate key events beyond the combo handler', function() {
          var keySpy = sinon.spy();

          document.addEventListener('keydown', keySpy);

          MockInteractions.pressEnter(keys);

          expect(keySpy.callCount).to.be.equal(0);
        });
      });

      suite('prevent default behavior of event', function() {
        setup(function() {
          keys = fixture('PreventKeys');
        });

        test('`defaultPrevented` is correctly set', function() {
          MockInteractions.pressEnter(keys);
          expect(keys.lastEvent.defaultPrevented).to.be.equal(true);
        });

        test('only 1 handler is invoked', function() {
          var aSpy = sinon.spy(keys, '_keyHandler');
          var shiftASpy = sinon.spy(keys, '_preventDefaultHandler');
          var event = new CustomEvent('keydown', {cancelable: true});
          // Combo `shift+a`.
          event.shiftKey = true;
          event.keyCode = event.code = 65;
          keys.dispatchEvent(event);

          expect(keys.keyCount).to.be.equal(1);
          expect(shiftASpy.called).to.be.true;
          expect(aSpy.called).to.be.false;
        });
      });

      suite('remove key behavior with null target', function() {
        test('add and remove a iron-a11y-keys-behavior', function() {
          var element = document.createElement('x-a11y-basic-keys');
          element.keyEventTarget = null;
          document.body.appendChild(element);
          document.body.removeChild(element);
        });
      });
    });
  </script>
</body>
</html>
